<template>
  <div :class="[`${classPrefix}`,className]" v-click-outside.capture="handleClickOutside">
    <template v-if="!onlyShowPane">
      <div :class="[`${classPrefix}__reference`]" ref="reference">
        <slot>
          <Input
            v-model="dateInputValue"
            ref="dateInput"
            :suffix="suffix"
            :prefix="prefix"
            :size="computedSize"
            :disabled="disabled"
            :clearable="clearable"
            :placeholder="placeholder"
            :readonly="readonlyInput"
            @enter="handleEnter"
            @click.native="handleFocus"
            @clear="handleClear"
          ></Input>
        </slot>
      </div>
      <transition :name="transition">
        <Drop
          v-show="visible && !disabled"
          ref="dropDown"
          :class="dropdownClassName"
          :reference="this.$refs.reference"
          :placement="placement"
          :min-width="false"
          :dropdownMatchSelectWidth="false"
          :render-html="renderHtml"
          v-model="visible"
        >
          <div :class="[`${classPrefix}__drop-down-inner`]">
            <date-pane
              ref="datePane"
              picker-type="date"
              :multiple="multiple"
              v-model="dateValue"
              :shortcuts="shortcuts"
              :is-range="isRange"
              :type="type"
              @change="handleDateValueChange"
            />
          </div>
        </Drop>
      </transition>
    </template>
    <template v-else>
      <div :class="[`${classPrefix}__drop-down-inner`]">
        <date-pane
          ref="datePane"
          picker-type="date"
          :multiple="multiple"
          v-model="dateValue"
          :shortcuts="shortcuts"
          :is-range="isRange"
          :type="type"
          @change="handleDateValueChange"
        />
      </div>
    </template>
  </div>
</template>

<script>
import Config from "/src/config";
import Input from "/src/components/input";
import { directive as clickOutside } from "v-click-outside-x";
import Drop from "/src/components/base/drop";
import DatePane from "../pane/datePane.vue";
import { dateFormat, formatToDate } from "/src/utils/dateTime";
import { findComponent } from "/src/utils/tools";

export default {
  name: "DatePicker",
  directives: { clickOutside },
  provide() {
    return {
      picker: this,
    };
  },
  props: {
    value: [String, Array, Date, Number],
    type: {
      type: String,
      default: "date",
      validator(value) {
        return [
          "date",
          "daterange",
          "datetime",
          "datetimerange",
          "year",
          "month",
          "week",
        ].includes(value);
      },
    },
    disabled: Boolean,
    readonly: Boolean,
    placeholder: String,
    size: {
      type: String,
      default() {
        return !this.$MONKEY || this.$MONKEY.size === ""
          ? "default"
          : this.$MONKEY.size;
      },
      validator(value) {
        return ["mini", "small", "default", "large"].includes(value);
      },
    },
    clearable: {
      type: Boolean,
      default: true,
    },
    suffix: {
      type: String,
      default: "moonStars",
    },
    prefix: {
      type: String,
      default: "",
    },
    transition: {
      type: String,
      default: "slideUp",
    },
    editable: {
      type: Boolean,
      default: true,
    }, // 文本框是否可以输入
    placement: {
      type: String,
      default: "bottom-start",
    },
    format: {
      type: String,
      default: "",
    },
    valueFormat: {
      type: String,
      default: "",
    },
    shortcuts: Array,
    disabledDate: Function,
    showWeekNumber: Boolean,
    renderHtml: {
      type: [HTMLElement, Boolean],
      default: function () {
        return false;
      },
    },
    open: Boolean, // 手动控制日期框的打开关闭
    multiple: Boolean, // 多选日期
    separator: {
      type: String,
      default: "~",
    }, // 两个日期之间的分隔符
    className: String, // 选择器的类名
    dropdownClassName: String, // 日期下拉框的类名
    onlyShowPane: Boolean, // 是否只显示pane日期框
  },
  data() {
    return {
      classPrefix: Config.classPrefix + "-date-picker",
      dateValue: [],
      dateInputValue: "",
      visible: false,
      form: findComponent(this, "Form"),
    };
  },
  computed: {
    isRange() {
      return this.type.includes("range");
    },
    readonlyInput() {
      return this.readonly || !this.editable;
    },
    computedSize() {
      if (this.size !== "default") return this.size;
      if (this.form && this.form.size !== "default") return this.form.size;
      return this.size;
    },
    defaultFormatType() {
      let result;
      switch (this.type) {
        case "date":
          result = "yyyy-MM-dd";
          break;
        case "daterange":
          result = "yyyy-MM-dd";
          break;
        case "datetime":
          result = "yyyy-MM-dd hh:mm:ss";
          break;
        case "datetimerange":
          result = "yyyy-MM-dd hh:mm:ss";
          break;
        case "year":
          result = "yyyy";
          break;
        case "month":
          result = "yyyy-MM";
          break;
        case "week":
          result = "yyyy-WW";
          break;
        default:
          result = "yyyy-MM-dd";
          break;
      }
      return result;
    },
    formatType() {
      if (this.format && this.format !== "timestamp") return this.format;
      return this.defaultFormatType;
    },
  },
  components: {
    Drop,
    DatePane,
    Input,
  },
  mounted() {
    this.initDateValue();
    this.updateInputValue();
    this.visible = this.open;
  },
  methods: {
    initDateValue(val) {
      let value = val || this.value;

      let valid = true;
      if (value && Array.isArray(value)) {
        value = value.map((item) => {
          const v = formatToDate(item, this.formatType);
          if (!v || (v && isNaN(v.getTime()))) valid = false;
          return v;
        });
      } else if (value) {
        const v = formatToDate(value, this.formatType);
        if (!v || (v && isNaN(v.getTime()))) valid = false;
        value = [v];
      } else {
        value = [];
      }

      if (!valid) return;

      if (this.multiple && this.type === "date") {
        this.dateValue = value;
      } else {
        if (this.isRange) {
          this.dateValue = value.length >= 2 ? [value[0], value[1]] : [];
        } else {
          this.dateValue = value.length >= 1 ? [value[0]] : [];
        }
      }
    },
    setValue(value) {
      if (!Array.isArray(value)) {
        value = new Date(value);
      }
      this.initDateValue(value);
      this.handleDateValueChange();
    },
    handleFocus() {
      this.dropDownVisible(true);
    },
    // 转换输入的inputvalue为标准日期格式
    handleEnter(value) {
      if (!value) return this.updateInputValue();

      const result = [];
      if (this.isRange && value.includes(this.separator))
        value = value.split(this.separator);
      else if (this.multiple) value = value.split(",");
      else value = [value];

      let valid = true;
      value.forEach((item) => {
        const v = formatToDate(item, this.formatType);
        if (valid) valid = !!v;
        result.push(v);
      });

      if (JSON.stringify(result) === JSON.stringify(this.dateValue)) return;
      if (result.length && valid) {
        this.initDateValue(result);
        this.handleDateValueChange();
      } else {
        this.updateInputValue();
      }
    },
    handleClickOutside(event) {
      if (this.visible) {
        if (this.renderHtml !== false) {
          const { $el } = this.$refs.dropDown;
          if ($el === event.target || $el.contains(event.target)) {
            return;
          }
        }

        this.dropDownVisible(false);
        this.handleEnter(this.dateInputValue);
        this.$emit("click-outside", event);
      }
    },
    dropDownVisible(visible) {
      if (this.readonly || this.open || this.disabled) return;
      this.visible = visible;
      visible &&
        this.$refs.datePane.isTime &&
        this.$refs.datePane.changeTimeAndDate();
    },
    handleClear() {
      this.dateValue = [];
      this.handleDateValueChange();
      this.$emit("clear");
    },
    updateInputValue() {
      const dateValue = this.dateValue.map((item) =>
        dateFormat(item, this.formatType)
      );
      if (this.multiple && this.type === "date") {
        this.dateInputValue = dateValue.join(",");
      } else {
        if (this.isRange) {
          if (dateValue.length === 2) {
            this.dateInputValue = `${dateValue[0]} ${this.separator} ${dateValue[1]}`;
          }
          if (dateValue.length === 0) this.dateInputValue = "";
        } else {
          this.dateInputValue = dateValue[0] || "";
        }
      }
    },
    focus() {
      this.$refs.dateInput && this.$refs.dateInput.focus();
    },
    blur() {
      this.$refs.dateInput && this.$refs.dateInput.blur();
    },
    dateClick(selectValue) {
      this.$emit("date-click", selectValue);
    },
    handleDateValueChange(value) {
      this.updateInputValue();
      value = value || this.dateValue;
      let result = value.map((item) => new Date(item));
      if (this.valueFormat)
        result = result.map((item) => dateFormat(item, this.valueFormat));

      if (this.isRange) {
        if (result.length === 2 || !result.length) {
          this.$emit("input", result);
          this.$emit("change", result);
        }
      } else if (this.multiple && this.type === "date") {
        this.$emit("input", result);
        this.$emit("change", result);
      } else {
        this.$emit("input", result[0] || "");
        this.$emit("change", result[0] || "");
      }
    },
  },
  watch: {
    value(newVal, oldVal) {
      if (JSON.stringify(newVal) === JSON.stringify(oldVal)) return;
      this.initDateValue();
      // this.handleDateValueChange();
      this.updateInputValue();
    },
    open(newVal) {
      this.visible = newVal;
    },
  },
};
</script>
